﻿/*************************************************************************
 *
 * Copyright (c) 2009-2015 Xuld. All rights reserved.
 * 
 * Project Url: http://work.xuld.net/circus
 * 
 * This source code is part of the Project Circus.
 * 
 * This code is licensed under The Circus License.
 * See the file License.html for the license details.
 * 
 * 
 * You must not remove this notice, or any other, from this software.
 *
 * 
 *************************************************************************/


using System;
using System.IO;
using System.Text;

namespace Circus.Utility {

    /// <summary>
    /// 提供处理路径和地址的相关方法。
    /// </summary>
    public static class PathHelper {

        /// <summary>
        /// 如果不存在则在路径后追加 /。
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static string appendSlash(string path) {
            if (String.IsNullOrEmpty(path)) {
                return "./";
            }

            char last = path[path.Length - 1];

            if (last == Path.DirectorySeparatorChar || last == Path.AltDirectorySeparatorChar) {
                return path;
            }

            return path + '/';
        }

        /// <summary>
        /// 获取指定路径相对指定路径的相对路径。
        /// </summary>
        /// <param name="basePath">当前的基路径。</param>
        /// <param name="path">当前的绝对路径。</param>
        /// <param name="seperatorChar">路径分隔符。</param>
        /// <returns>返回相对位置。</returns>
        /// <example>
        /// relative(@"D:\Windows\Web\Wallpaper\aa.css", @"D:\Windows\regedit.exe" ) => @"..\..\regedit.exe"
        /// </example>
        public static string relative(string basePath, string path, char seperatorChar = '/') {
            basePath = normalizePath(basePath);
            path = normalizePath(path);
            string[] fromParts = basePath.Split('/');
            string[] toParts = path.Split('/');

            int length = Math.Min(fromParts.Length, toParts.Length);
            int samePartsLength = length;
            for (int i = 0; i < length; i++) {
                if (fromParts[i] != toParts[i]) {
                    samePartsLength = i;
                    break;
                }
            }

            StringBuilder sb = new StringBuilder();

            for (int i = samePartsLength + 1; i < fromParts.Length; i++) {
                sb.Append("../");
            }

            for (int i = samePartsLength; i < toParts.Length; i++) {
                sb.Append(toParts[i]);
                sb.Append('/');
            }

            if (sb.Length == 0) {
                return ".";
            }

            sb.Length--;

            return sb.ToString();
        }

        /// <summary>
        /// 将两个路径合为一个路径。
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        public static string combine(string left, string right) {
            return normalizePath(left + '/' + right);
        }

        /// <summary>
        /// 获取指定路径下的相对路径所指示的实际路径。
        /// </summary>
        /// <param name="basePath">解析的基础路径。</param>
        /// <param name="relativePath">解析的相对路径。</param>
        /// <returns>返回绝对位置。</returns>
        public static string resolve(string basePath, string relativePath) {
            return Path.IsPathRooted(relativePath) ? relativePath : normalizePath(PathHelper.basePath(basePath) + relativePath);
        }

        /// <summary>
        /// 获取指定路径下的相对路径所指示的实际路径。
        /// </summary>
        /// <param name="paths">要解析的路径。</param>
        /// <returns>返回绝对位置。</returns>
        public static string resolve(params string[] paths) {
            var path = paths[0];
            for (int i = 1; i < paths.Length; i++) {
                path = resolve(path, paths[i]);
            }
            return path;
        }

        static int normalizeArray(string[] parts, int end = 0) {
            // resolves . and .. elements in a path array with directory names there
            // must be no slashes, empty elements, or device names (c:\) in the array
            // (so also no leading and trailing slashes - it does not distinguish
            // relative and absolute paths)
            // if the path tries to go above the root, `up` ends up > 0
            int topLevelAboveRoot = 0;
            for (int i = parts.Length - 1; i >= end; i--) {
                string last = parts[i];
                if (last == ".") {
                    parts[i] = String.Empty;
                } else if (last == "..") {
                    parts[i] = String.Empty;
                    topLevelAboveRoot++;
                } else if (topLevelAboveRoot > 0) {
                    parts[i] = String.Empty;
                    topLevelAboveRoot--;
                }
            }

            return topLevelAboveRoot;
        }

        static string[] splitPath(string path) {
            // Split a filename into [root, dir, basename, ext], unix version
            // 'root' is just a slash, or nothing.
            return path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
        }

        /// <summary>
        /// 规划化指定的文件路径。
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static string normalizePath(string path) {

            if (String.IsNullOrEmpty(path)) {
                return String.Empty;
            }

            char c;
            string[] parts = splitPath(path);

            c = path[0];

            int end = 0;
            if (parts[0].Length == 0 || parts[0] == "~") {
                end = 1;
            } else if (parts[0][parts[0].Length - 1] == ':') {
                end = parts[0].Length > 2 && parts.Length > 1 && parts[1].Length == 0 ? 2 : 1;
            }

            c = path[path.Length - 1];
            bool trailingSlash = c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
            int count = normalizeArray(parts, end);

            StringBuilder sb = new StringBuilder(parts.Length * 20);

            if (end == 0) {
                while (count-- > 0) {
                    sb.Append("../");
                }
            } else if (end == 1) {
                sb.Append(parts[0]);
                sb.Append('/');
            } else if (end == 2) {
                sb.Append(parts[0]);
                sb.Append('/');
                sb.Append('/');
            }


            for (int i = end; i < parts.Length; i++) {
                if (parts[i].Length > 0) {
                    sb.Append(parts[i]);
                    sb.Append('/');
                }
            }

            if (sb.Length == 0) {
                if (end == 0) {
                    sb.Append('.');
                }
            } else if (!trailingSlash) {
                sb.Length--;
            }

            return sb.ToString();
        }

        /// <summary>
        /// 获取指定路径的基路径。
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static string basePath(string path) {
            string newPath = System.Text.RegularExpressions.Regex.Replace(path, @"[\\/][^\\/]*$", "/");
            if (newPath.Length == path.Length) {
                if (path.IndexOf('\\') == -1 && path.IndexOf('/') == -1) {
                    newPath = String.Empty;
                }
            }

            return newPath;
        }

        /// <summary>
        /// 修改一个路径中的目录，并返回新位置。
        /// </summary>
        /// <param name="path">要处理的文件路径。</param>
        /// <param name="destDirectoryPath">移动的目标目录位置。</param>
        /// <returns>新的位置字符串。</returns>
        public static string changeDirectory(string path, string destDirectoryPath) {
            return Path.Combine(destDirectoryPath, Path.GetFileName(path));
        }

        /// <summary>
        /// 修改一个路径中的文件名，并返回新位置。
        /// </summary>
        /// <param name="path">要处理的文件路径。</param>
        /// <param name="destFileName">新文件名。</param>
        /// <returns>更新后的路径。</returns>
        public static string changeFileName(string path, string destFileName) {
            if (!(destFileName.EndsWith("/") || destFileName.EndsWith("\\")))
                path = Path.GetDirectoryName(path);
            return Path.Combine(path, destFileName);
        }

        /// <summary>
        /// 修改一个路径中的文件名(不包括扩展名)，并返回新位置。
        /// </summary>
        /// <param name="path">要处理的文件路径。</param>
        /// <param name="destFileName">新文件名(不包括扩展名)。</param>
        /// <returns>更新后的路径。</returns>
        public static string changeFileNameWithoutExtension(string path, string destFileName) {
            return changeFileName(path, Path.ChangeExtension(destFileName, Path.GetExtension(path)));
        }

        /// <summary>
        /// 在一个路径中的文件名中追加指定的内容(不修改扩展名)，并返回新位置。
        /// </summary>
        /// <param name="path">要处理的文件路径。</param>
        /// <param name="value">新文件名(不包括扩展名)。</param>
        /// <returns>更新后的路径。</returns>
        public static string appendFileName(string path, string value) {
            if (path == null)
                return value;
            int i = path.LastIndexOf('.');
            if (i == -1) {
                return path + value;
            }

            return path.Insert(i, value);
        }

    }
}
